/**
 * This file is part of Adaptools.
 * 
 * Adaptools is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Adaptools is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 * along with Adaptools.  If not, see http://www.gnu.org/licenses/. 
 */
package org.adaptools.filecontroler.impl;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Collection;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.adaptools.base.Mechanism;
import org.adaptools.base.Queue;
import org.adaptools.base.Rule;
import org.adaptools.base.context.AdaptoolsContextFactory;
import org.adaptools.base.exception.AdaptoolsException;
import org.adaptools.filecontroler.util.AdaptoolsFileControlerMessages;
import org.adaptools.repository.base.Repository;
import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;

/**
 * Abstract Class to FileControler
 * 
 * @author Denys G. Santos (gsdenys@gmail.com)
 * @since 3.0.0
 */
public abstract class AbstractFileControler {
	Logger LOG = Logger.getLogger(AbstractFileControler.class);

	protected Repository repository = null;
	protected String mechanismUUID = null;

	/**
	 * Load Adaptools package
	 * 
	 * @param filePath
	 * @return boolean
	 */
	protected void loadPackage(String filePath) throws AdaptoolsException {
		// verify if file path is a adaptools package type
		if (!this.getFileType(filePath).equals(AdaptoolsFileType.PACKAGE)) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_UNABLE_TO_LOAD_PACKAGE
							.getId(), filePath);
		}

		try {
			// load pakage file (This is a zip Format renamed with the .adp
			// suffix
			ZipInputStream zin = new ZipInputStream(new FileInputStream(
					filePath));
			// create a zip entry to iterate entries
			ZipEntry entry;

			// iterate from zip entries obtaining the input stream reader and
			// calling
			while ((entry = zin.getNextEntry()) != null) {
				AdaptoolsFileType type = this.getFileType(entry.getName());
				InputStreamReader isr = new InputStreamReader(zin);
				BufferedReader in = new BufferedReader(isr);

				if (type.equals(AdaptoolsFileType.INPUT)) {
					// load Input
					this.loadInput(in);
				} else if (type.equals(AdaptoolsFileType.OUTPUT)) {
					// load the output
					this.loadOutput(in);
				} else if (type.equals(AdaptoolsFileType.MACHINE)) {
					// load Machine
					this.loadMachine(in);
				} else if (type.equals(AdaptoolsFileType.TRANSDUCER)) {
					// load transducer
					this.loadTransducer(in);
				} else if (type.equals(AdaptoolsFileType.SEMANTIC_ACTION)) {
					this.loadSemanticAction(in);
				} else if (type.equals(AdaptoolsFileType.HELPER)) {
					this.loadHelper(in);
				}

				// close entry
				zin.closeEntry();
			}

			// close package
			zin.close();

			// Start Machine Package Verification
			Mechanism mechanism = this.repository.get(this.mechanismUUID);
			Collection<Rule> ruleCollection = mechanism.getRuleCollection();
			if (ruleCollection == null || ruleCollection.size() == 0) {
				throw new AdaptoolsException(
						AdaptoolsFileControlerMessages.ERROR_PACKAGE_NOT_CONTAIN_MACHINE
								.getId());
			}

		} catch (FileNotFoundException e) {
			LOG.error("File not Found: " + filePath);
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_PACKAGE_NOT_FOUND
							.getId(),
					filePath);
		} catch (IOException e) {
			LOG.error("Unable to load Package");
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_UNABLE_TO_LOAD_PACKAGE
							.getId(), filePath);
		}

	}

	/**
	 * Load Adaptools Machine
	 * 
	 * @param filePath
	 * @return boolean
	 * 
	 * @throws AdaptoolsException
	 */
	protected void loadMachine(String filePath) throws AdaptoolsException {
		// verify if file path is a adaptools machine type
		if (!this.getFileType(filePath).equals(AdaptoolsFileType.MACHINE)) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_LOAD_UNABLE_TO_LOAD_MACHINE
							.getId(), filePath);
		}

		// Try to load machine
		try {
			// load File
			FileReader fileReader = new FileReader(filePath);
			// load the Buffered Reader
			BufferedReader in = new BufferedReader(fileReader);
			// call local machine load
			this.loadMachine(in);

		} catch (FileNotFoundException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_MACHINE_NOT_FOUND
							.getId(),
					filePath);
		}
	}

	/**
	 * Load Adaptools Input
	 * 
	 * @param filePath
	 */
	protected void loadHelper(String filePath) throws AdaptoolsException {
		// verify if file path is a adaptools Imput type
		if (!this.getFileType(filePath).equals(AdaptoolsFileType.HELPER)) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_UNABLE_TO_SAVE_HELPER
							.getId(), filePath);
		}

		// Try to load helper
		try {
			// load File
			FileReader fileReader = new FileReader(filePath);
			// load the Buffered Reader
			BufferedReader in = new BufferedReader(fileReader);
			// call local semantic action load
			this.loadHelper(in);

		} catch (FileNotFoundException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_HELPER_NOT_FOUND
							.getId(),
					filePath);
		}
	}

	/**
	 * Load Adaptools Input
	 * 
	 * @param filePath
	 */
	protected void loadInput(String filePath) throws AdaptoolsException {
		// verify if file path is a adaptools Imput type
		if (!this.getFileType(filePath).equals(AdaptoolsFileType.INPUT)) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_UNABLE_TO_SAVE_INPUT
							.getId(), filePath);
		}

		// Try to load Input
		try {
			// load File
			FileReader fileReader = new FileReader(filePath);
			// load the Buffered Reader
			BufferedReader in = new BufferedReader(fileReader);
			// call local semantic action load
			this.loadInput(in);

		} catch (FileNotFoundException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_INPUT_NOT_FOUND
							.getId(),
					filePath);
		}
	}

	/**
	 * Load Adaptools Output
	 * 
	 * @param filePath
	 */
	protected void loadOutput(String filePath) throws AdaptoolsException {
		// verify if file path is a adaptools output type
		if (!this.getFileType(filePath).equals(AdaptoolsFileType.OUTPUT)) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_UNABLE_TO_SAVE_OUTPUT
							.getId(), filePath);
		}

		// Try to load output
		try {
			// load File
			FileReader fileReader = new FileReader(filePath);
			// load the Buffered Reader
			BufferedReader in = new BufferedReader(fileReader);
			// call local semantic action load
			this.loadOutput(in);

		} catch (FileNotFoundException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_OUTPUT_NOT_FOUND
							.getId(),
					filePath);
		}
	}

	/**
	 * Load Adaptools Transducer
	 * 
	 * @param filePath
	 */
	protected void loadTransducer(String filePath) throws AdaptoolsException {
		// verify if file path is a adaptools transducer type
		if (!this.getFileType(filePath).equals(AdaptoolsFileType.TRANSDUCER)) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_UNABLE_TO_SAVE_TRANSDUCER
							.getId(), filePath);
		}

		// Try to load transducer
		try {
			// load File
			FileReader fileReader = new FileReader(filePath);
			// load the Buffered Reader
			BufferedReader in = new BufferedReader(fileReader);
			// call local semantic action load
			this.loadTransducer(in);

		} catch (FileNotFoundException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_TRANSDUCER_NOT_FOUND
							.getId(), filePath);
		}
	}

	/**
	 * Load Adaptools Semantic Action
	 * 
	 * @param filePath
	 */
	protected void loadSemanticAction(String filePath)
			throws AdaptoolsException {
		// verify if file path is a adaptools Semantic Action type
		if (!this.getFileType(filePath).equals(
				AdaptoolsFileType.SEMANTIC_ACTION)) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_UNABLE_TO_SAVE_SEMANTIC_ACTION
							.getId(), filePath);
		}

		// Try to load semantic Action
		try {
			// load File
			FileReader fileReader = new FileReader(filePath);
			// load the Buffered Reader
			BufferedReader in = new BufferedReader(fileReader);
			// call local semantic action load
			this.loadSemanticAction(in);

		} catch (FileNotFoundException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_SEMANTIC_ACTION_NOT_FOUND
							.getId(), filePath);
		}

	}

	/**
	 * Load Adaptools Machine
	 * 
	 * @param in
	 * @return boolean
	 */
	protected void loadMachine(BufferedReader in) throws AdaptoolsException {
		String lineRead;
		Mechanism mechanism = this.repository.get(this.mechanismUUID);

		LOG.debug("Loading Machine to the Mechanism UUID: "
				+ this.mechanismUUID);

		ApplicationContext context = AdaptoolsContextFactory.getContext();

		int row = 0;
		int element = 0;
		try {
			while ((lineRead = in.readLine()) != null) {

				Rule rule = (Rule) context.getBean("adaptools.repository.rule");

				LOG.debug("Load Rule: '" + lineRead + "'  whith UUID: "
						+ rule.getId());

				// If blank line or is a comment, then continue in the next line
				if (lineRead.trim().equals("") || lineRead.startsWith("#")) {
					continue;
				}

				// split line with ;
				String[] line = lineRead.split(";");

				// add 1 to a number of read line and read the element
				row++;

				// add 1 to number of read element and read the element header
				element++;
				rule.setHeader(line[0]);

				// add 1 to number of read element and read the element Source
				element++;
				rule.setSource(line[1]);

				// add 1 to number of read element and read the element Input
				element++;
				rule.setInput(line[2]);

				// add 1 to number of read element and read the element Target
				element++;
				rule.setTarget(line[3]);

				// add 1 to number of read element and read the element Push
				element++;
				rule.setPush(line[4]);

				// add 1 to number of read element and read the element Output
				element++;
				rule.setOutput(line[5]);

				// add 1 to number of read element and read the element Adaptive
				element++;
				rule.setAdaptive(line[6]);

				// add the rule to the mechanism
				mechanism.add(rule);

				// roll back the element to zero.
				element = 0;
			}
		} catch (IOException e) {
			LOG.error("Unable to load Machine");
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_UNABLE_TO_SAVE_MACHINE
							.getId());
		} catch (ArrayIndexOutOfBoundsException e) {
			String error = "[Row: " + row + " Element: " + element + "]";
			LOG.error("Unable to load Machine - Please check your Machine Implementation! "
					+ error);
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_UNABLE_TO_SAVE_MACHINE
							.getId(), error);
		}
	}

	/**
	 * Load Adaptools Helper
	 * 
	 * @param in
	 * @return boolean
	 */
	private void loadHelper(BufferedReader in) throws AdaptoolsException {
		try {
			// read the first line
			String lineRead = null;

			StringBuilder builder = new StringBuilder();

			while ((lineRead = in.readLine()) != null) {
				// put the content in the queue
				builder.append(lineRead);
			}

			this.repository.setHelper(builder.toString());
		} catch (IOException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_LOAD_UNABLE_TO_LOAD_INPUT
							.getId());
		}
	}

	/**
	 * Load Adaptools Input
	 * 
	 * @param in
	 * @return boolean
	 */
	protected void loadInput(BufferedReader in) throws AdaptoolsException {
		// get repository mechanism based in UUID
		Mechanism mechanism = this.repository.get(this.mechanismUUID);

		try {
			// read the first line
			String lineRead = null;
			// get the queue from mechanism
			Queue queue = mechanism.getQueue();

			while ((lineRead = in.readLine()) != null) {
				// put the content in the queue
				queue.add(lineRead);
			}

		} catch (IOException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_LOAD_UNABLE_TO_LOAD_INPUT
							.getId());
		}
	}

	/**
	 * Load Adaptools Output
	 * 
	 * @param in
	 * @return boolean
	 */
	protected void loadOutput(BufferedReader in) throws AdaptoolsException {
		// get repository mechanism based in UUID
		Mechanism mechanism = this.repository.get(this.mechanismUUID);

		try {
			// read the first line
			String lineRead = null;
			// get the output from mechanism
			Queue queue = mechanism.getOutput();

			while ((lineRead = in.readLine()) != null) {
				// put the content in the queue
				queue.add(lineRead);
			}

		} catch (IOException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_LOAD_UNABLE_TO_LOAD_OUTPUT
							.getId());
		}
	}

	/**
	 * Load Adaptools Transducer
	 * 
	 * @param in
	 */
	protected void loadTransducer(BufferedReader in) throws AdaptoolsException {
		// get repository mechanism based in UUID
		Mechanism mechanism = this.repository.get(this.mechanismUUID);

		try {
			// read the first line
			String lineRead = null;
			// get the output from mechanism
			Queue queue = mechanism.getTransducer();

			while ((lineRead = in.readLine()) != null) {
				// put the content in the queue
				queue.add(lineRead);
			}

		} catch (IOException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_LOAD_UNABLE_TO_LOAD_OUTPUT
							.getId());
		}
	}

	/**
	 * Load Adaptools Semantic Action
	 * 
	 * @param filePath
	 * @return boolean
	 */
	protected void loadSemanticAction(BufferedReader in)
			throws AdaptoolsException {

		try {
			// read the first line
			String lineRead = null;

			StringBuilder builder = new StringBuilder();

			while ((lineRead = in.readLine()) != null) {
				// put the content in the queue
				builder.append(lineRead);
			}

			this.repository.setSemantic(builder.toString());
		} catch (IOException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_LOAD_UNABLE_TO_LOAD_INPUT
							.getId());
		}

	}

	/**
	 * Save Adaptools Helper
	 * 
	 * @param help
	 * @param filePath
	 */
	protected void saveHelper(String help, String filePath)
			throws AdaptoolsException {
		File file = new File(filePath);
		try {
			PrintStream ps = new PrintStream(file);
			ps.println(help);
			ps.close();
		} catch (IOException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_UNABLE_TO_SAVE_HELPER
							.getId());
		}
	}

	/**
	 * Save Adaptools Transducer
	 * 
	 * @param trans
	 * @param filePath
	 */
	protected void saveTransducer(String trans, String filePath)
			throws AdaptoolsException {
		File file = new File(filePath);
		try {
			PrintStream ps = new PrintStream(file);
			ps.println(trans);
			ps.close();
		} catch (IOException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_UNABLE_TO_SAVE_TRANSDUCER
							.getId());
		}
	}

	/**
	 * Save Adaptools output
	 * 
	 * @param out
	 * @param filePath
	 */
	protected void saveOutput(String out, String filePath)
			throws AdaptoolsException {
		File file = new File(filePath);
		try {
			PrintStream ps = new PrintStream(file);
			ps.println(out);
			ps.close();
		} catch (IOException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_UNABLE_TO_SAVE_OUTPUT
							.getId());
		}
	}

	/**
	 * Save Adaptools Input
	 * 
	 * @param in
	 * @param filePath
	 */
	protected void saveInput(String in, String filePath)
			throws AdaptoolsException {
		File file = new File(filePath);
		try {
			PrintStream ps = new PrintStream(file);
			ps.println(in);
			ps.close();
		} catch (IOException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_UNABLE_TO_SAVE_INPUT
							.getId());
		}
	}

	/**
	 * Save Adaptools SemanticAction
	 * 
	 * @param sa
	 * @param filePath
	 */
	protected void saveSemanticAction(String sa, String filePath)
			throws AdaptoolsException {
		File file = new File(filePath);
		try {
			PrintStream ps = new PrintStream(file);
			ps.println(sa);
			ps.close();
		} catch (IOException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_UNABLE_TO_SAVE_SEMANTIC_ACTION
							.getId());
		}
	}

	/**
	 * Save Adaptools machine
	 * 
	 * @param mechanism
	 * @param filePath
	 */
	protected void saveMachine(Mechanism mechanism, String filePath)
			throws AdaptoolsException {
		File file = new File(filePath);
		try {
			PrintStream ps = new PrintStream(file);
			for(Rule rule : mechanism.getAdaptiveRules()){
				String str = rule.toString();
				ps.println(str);
			}
			ps.close();
		} catch (IOException e) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_FILE_UNABLE_TO_SAVE_SEMANTIC_ACTION
							.getId());
		}
	}
	
		
	/**
	 * Verify the type of file
	 * 
	 * @return AdaptoolsFileType
	 */
	protected AdaptoolsFileType getFileType(String filePath)
			throws AdaptoolsException {

		// verify if path exist
		if (filePath == null || filePath.trim().equals("")) {
			throw new AdaptoolsException(
					AdaptoolsFileControlerMessages.ERROR_PATH_NULL_OR_EMPTY
							.getId());
		}

		// Get the type of filepath
		if (filePath.endsWith(AdaptoolsFileType.PACKAGE.getFileSuffix())) {
			return AdaptoolsFileType.PACKAGE;
		} else if (filePath.endsWith(AdaptoolsFileType.INPUT.getFileSuffix())) {
			return AdaptoolsFileType.INPUT;
		} else if (filePath.endsWith(AdaptoolsFileType.MACHINE.getFileSuffix())) {
			return AdaptoolsFileType.MACHINE;
		} else if (filePath.endsWith(AdaptoolsFileType.SEMANTIC_ACTION
				.getFileSuffix())) {
			return AdaptoolsFileType.SEMANTIC_ACTION;
		} else if (filePath.endsWith(AdaptoolsFileType.OUTPUT.getFileSuffix())) {
			return AdaptoolsFileType.OUTPUT;
		} else if (filePath.endsWith(AdaptoolsFileType.TRANSDUCER
				.getFileSuffix())) {
			return AdaptoolsFileType.TRANSDUCER;
		} else if (filePath.endsWith(AdaptoolsFileType.HELPER.getFileSuffix())) {
			return AdaptoolsFileType.HELPER;
		}

		return null;
	}

}
